Skip to content

feat(fast-inbox): streaming inbox propose validation, unwired (A-1378) - #24773

Closed
spalladino wants to merge 7 commits into
spl/a-1377-inbox-bucketsfrom
spl/a-1378-propose-validation
Closed

feat(fast-inbox): streaming inbox propose validation, unwired (A-1378)#24773
spalladino wants to merge 7 commits into
spl/a-1377-inbox-bucketsfrom
spl/a-1378-propose-validation

Conversation

@spalladino

@spalladino spalladino commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Stacked on #24771 (A-1377). AZIP-22 Fast Inbox, FI-08.

What

Implements the AZIP's censorship assert as ProposeLib.validateInboxConsumption — a new library function that is not yet called: today's inbox.consume() / inHash check in propose is untouched, so this is dead code until the flip wires it in.

Given the checkpoint header's inboxRollingHash, an unsigned calldata bucket hint, the proposed slot, and the parent checkpoint's cumulative consumed total (which the flip will source from the temp checkpoint log), the function enforces:

  1. End anchoring: the header's rolling hash must equal the snapshot in inbox.getBucket(hint) (Rollup__InvalidInboxRollingHash). The hint is a lookup aid only — a wrong hint reverts, it cannot change what gets accepted — and getBucket itself rejects hints beyond the current bucket or already overwritten in the ring. A checkpoint consuming nothing references the same bucket as its parent (the genesis bucket for the first checkpoint), so there is no base case.
  2. Mandatory consumption: the first unconsumed bucket (hint + 1) must be absent, past the cutoff, or cap-escaped (Rollup__UnconsumedInboxMessages). The cutoff is the build-frame start minus INBOX_LAG_SECONDS: a checkpoint proposed in slot S is built during slot S-1 (proposer pipelining), and validators are not required to act on buckets younger than one L1 slot at build start, so cutoff = toTimestamp(S-1) - 12. Cap escape allows stopping when consuming through the next bucket would exceed MAX_L1_TO_L2_MSGS_PER_CHECKPOINT (1024) messages since the parent's total. Both comparisons are exact-boundary tested.

The function performs no Inbox write. It is a pure view that returns the consumed cumulative total (bucket.totalMsgCount). The flip (FI-14) stores that consumed position as part of the per-checkpoint temp-log record, extended to {inboxRollingHash, inboxMsgTotal, inboxConsumedBucket} and written by propose; that record is the authoritative consumed position and is prune-consistent, since temp logs rewind with the pending chain. Two new checks guard the returned value: consumption must move forward (Rollup__InboxConsumptionBehindParent, equal allowed — a proposal cannot consume behind its parent, and the check precedes the delta subtractions so a backwards proposal reverts descriptively rather than underflowing), and the consumed delta cannot exceed MAX_L1_TO_L2_MSGS_PER_CHECKPOINT in one checkpoint (Rollup__TooManyInboxMessagesConsumed). The Inbox-side proven-consumed cache that ring overwrite protection needs moved to FI-20, anchored to the proven tip rather than the pending chain (decided 2026-07-17): a pointer advanced with the pending chain is not prune-safe, since after a prune it would sit ahead of what the replacement chain consumed.

INBOX_LAG_SECONDS = 12 and MAX_L1_TO_L2_MSGS_PER_CHECKPOINT = 1024 are defined at file level in ProposeLib for now; they mirror the protocol constants and should move into the generated Constants library once the Solidity emitter allow-list includes them.

Testing

New ProposeInboxConsumption.t.sol (15 tests) drives the library function through a harness that owns the Inbox and TimeLib storage, covering the roadmap done-when plus boundaries:

  • empty Inbox (hint 0 / hash 0 passes and returns 0; non-zero hash rejected),
  • exact-cutoff bucket (timestamp == cutoff must be consumed; cutoff + 1 need not),
  • cap escape (1025 messages spill into buckets of 256+1: stopping at bucket 4 = exactly 1024 escapes and returns the full cap; stopping at bucket 3 = 1024-through-next does not; parent total shifts the arithmetic and kills the escape),
  • cap upper bound (consuming more than 1024 in one checkpoint from a fresh parent reverts Rollup__TooManyInboxMessagesConsumed),
  • moves forward (a proposal whose referenced bucket total sits behind the parent reverts Rollup__InboxConsumptionBehindParent; an equal reference consumes nothing and returns the unchanged total),
  • stale hash (previous bucket's hash against the current bucket) and unknown hash rejection, plus out-of-window hints (future bucket, ring-overwritten bucket),
  • return value: the successful paths assert the consumed cumulative total the flip will record (0, 3, cap).

The three former consumed-pointer tests are removed along with the pointer. Full forge test at the current rebased tip: 890 passed, 0 failed, 3 skipped (the previously noted testGetEpochProofPublicInputsVerifiesHeaders failure inherited from the base has since been fixed).

@spalladino
spalladino requested a review from just-mitch as a code owner July 17, 2026 18:24
@spalladino
spalladino force-pushed the spl/a-1377-inbox-buckets branch from 630c509 to 20656f8 Compare July 17, 2026 19:38
@spalladino
spalladino force-pushed the spl/a-1378-propose-validation branch from aa85a03 to 531e71a Compare July 17, 2026 19:38
@spalladino
spalladino force-pushed the spl/a-1377-inbox-buckets branch from 20656f8 to d12875f Compare July 17, 2026 20:04
@spalladino
spalladino force-pushed the spl/a-1378-propose-validation branch from 531e71a to b011406 Compare July 17, 2026 20:04
@spalladino
spalladino force-pushed the spl/a-1377-inbox-buckets branch from d12875f to 68714ab Compare July 17, 2026 23:29
@spalladino
spalladino force-pushed the spl/a-1378-propose-validation branch 2 times, most recently from 57ce718 to b853b87 Compare July 18, 2026 04:52
@spalladino
spalladino force-pushed the spl/a-1377-inbox-buckets branch from ec1326a to db14374 Compare July 18, 2026 05:22
@spalladino
spalladino force-pushed the spl/a-1378-propose-validation branch 2 times, most recently from 6e642ec to a003504 Compare July 19, 2026 14:05
@spalladino
spalladino force-pushed the spl/a-1377-inbox-buckets branch from db14374 to a93a3eb Compare July 19, 2026 14:05
@spalladino
spalladino force-pushed the spl/a-1378-propose-validation branch from a003504 to e1600a5 Compare July 19, 2026 17:57
@spalladino
spalladino force-pushed the spl/a-1377-inbox-buckets branch 2 times, most recently from 9be7a9f to 695a55a Compare July 19, 2026 20:48
@spalladino
spalladino force-pushed the spl/a-1378-propose-validation branch 2 times, most recently from 5088258 to d904bee Compare July 20, 2026 15:30
@spalladino
spalladino force-pushed the spl/a-1377-inbox-buckets branch from 13d85e2 to d747a5c Compare July 20, 2026 17:28
@spalladino
spalladino force-pushed the spl/a-1378-propose-validation branch 2 times, most recently from 41e7506 to a5484bb Compare July 20, 2026 21:21
@spalladino
spalladino force-pushed the spl/a-1377-inbox-buckets branch from d747a5c to 27e2670 Compare July 20, 2026 21:21
@spalladino
spalladino force-pushed the spl/a-1378-propose-validation branch from a5484bb to 5a9c4e0 Compare July 21, 2026 02:58
@spalladino
spalladino force-pushed the spl/a-1377-inbox-buckets branch 2 times, most recently from 258788d to e7fa949 Compare July 21, 2026 03:39
@spalladino
spalladino force-pushed the spl/a-1378-propose-validation branch 2 times, most recently from 7f41bee to df088ff Compare July 27, 2026 20:53
Place the IInbox import after IRollup so solhint's imports-order rule passes.
… (A-1378)

Remove the transitional skip added at the per-block-bundle branch. multi_proof was
observed passing in CI at this content (2-CPU, 65s on 2026-07-19) and passes on
next/merge-train, so the constrained-CPU slowdown introduced earlier in the stack is
resolved by here. The skip stays protective over the intervening branches where we
have no passing observation; from this branch upward the test runs normally.
…(A-1378)

A bucket keeps absorbing messages for the whole L1 block that opened it, so a
reference to the current bucket is not a reference to any lasting snapshot. A lone
designated proposer could bundle send(A) -> propose(hash-after-A) -> send(B) into one
L1 transaction: L1 accepts the checkpoint, then the trailing send overwrites the
bucket in place, leaving the checkpoint committed to a rolling hash that exists
neither on L1 nor in any node, since nodes only ever observe a bucket's end-of-block
state. No honest node can resolve the consumed position, so the pending chain stalls
until the checkpoint is pruned.

validateInboxConsumption now requires the referenced bucket to be settled, which is
the negation of the Inbox's own rollover condition: the genesis bucket (which never
absorbs), a bucket whose L1 block has passed, or a full bucket whose successor takes
the next message. The per-bucket cap moves to IInbox.sol so the Inbox and the propose
path share one definition without the rollup libraries depending on the Inbox
implementation. Honest proposers never reference a bucket younger than
INBOX_LAG_SECONDS, so nothing legitimate is excluded.
@spalladino

Copy link
Copy Markdown
Contributor Author

Superseded by #25036.

The Fast Inbox stack has been regrouped from 20 per-issue PRs (plus 2 umbrellas) down to 3 area PRs plus an umbrella, and rebased onto merge-train/spartan including #25007 (the noir-projects fnd//labs/ split). This PR's diff is preserved verbatim as a single squashed commit in #25036, and this description is preserved in that commit's message.

New structure: #25036 (circuits + L1) → #25037 (node + flip) → #25038 (cleanup), with #25039 as the full-stack umbrella. The original branch for this PR is left on the remote as a recovery point.

Closing here to cut the rebase and review overhead of maintaining 22 PRs; the work is not abandoned.

@spalladino spalladino closed this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant